package com.lucas.shopping.seckill.web;

import com.lucas.common.result.ResultRtn;
import com.lucas.shopping.config.Constants;
import com.lucas.shopping.seckill.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.Scope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @auther Lucas & lisw
 * @date 2020/12/12 23:08
 */
@RestController()
@RequestMapping("/redisZookeeper")
@Slf4j
public class RedisZookeeperController {

    @Autowired
    private ProductService productService;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private ZooKeeper zooKeeper;


    private static volatile ConcurrentHashMap<String,Boolean> params = new ConcurrentHashMap<>();

    @PostMapping("/secKill/{productId}/{userId}")
    public ResultRtn secKill(@PathVariable("productId") Long productId,
                             @PathVariable("userId") Long userId,
                             @RequestParam("isAndEx") String isAndEx
    ) throws KeeperException, InterruptedException {
        //JVM的Key
        String jvmKey = Constants.local_product_key_prefix+productId;
        //判断JVM缓存
        if(params.containsKey(jvmKey)){
            log.info("本地缓存已售完");
            return ResultRtn.ok("此商品已经售完");
        }
        //商品库存的Redis的Key
        String redisKey = Constants.redis_product_key_prefix+productId;
        //Redis库存减一
        Long count =  redisTemplate.opsForValue().decrement(redisKey);
        try{
        /**
         * 【isAndEx】纯属为了测试模拟并发使用，让我们能够看到zookeeper给我们带来的效果
         * 场景如下：
         * 某商品库存为1  启动两个应用  应用1端口：9001   应用2端口：9002
         * A线程在应用1，【isAndEx】传值为1，则A线程在会在此处卡顿10秒，此时Redis库存已为0，10秒倒计时开始
         *【A线程还在卡顿】- B线程在应用2，【isAndEx】传值为为0，则B线程，查询Redis库存为0，返回商品已售完，且应用2的JVM缓存【params】已设置为true
         * A线程在应用1-模拟的10秒结束，抛出异常，减1的库存要加回来。库存变为1，
         * 当因为应用1发生异常，将库存+1的时候，如果没有zookeeper将应用2的JVM缓存【params】删掉，那么后面所有的请求进入到B机器，则全部不会抢到商品。
         * 至此，则会出现少卖现象。
         */
        if("1".equals(isAndEx)){
            Thread.sleep(10000);
            throw new Exception("模拟异常,已延迟5秒");
        }
        if(count<0){
            redisTemplate.opsForValue().increment(redisKey);
            params.put(Constants.local_product_key_prefix+productId,true);
            // zookeeper 中设置售完标记， zookeeper 节点数据格式 product/1 true
            String productPath = Constants.zoo_product_key_prefix + "/" + productId;
            if(zooKeeper.exists(productPath, true)==null){
                zooKeeper.create(productPath, "true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                zooKeeper.exists(productPath,true);
            }
            return ResultRtn.ok("此商品已经售完");
        }
            productService.updateStock(productId);
        }catch (Exception e){
            // 通过 zookeeper 回滚其他服务器的 JVM 缓存中的商品售完标记
            String path = Constants.zoo_product_key_prefix + "/" + productId;
            if (zooKeeper.exists(path, true) != null){
                zooKeeper.setData(path, "false".getBytes(), -1);
            }
            params.remove(jvmKey);
            //出现异常Redis减的1，在加回来
            redisTemplate.opsForValue().increment(redisKey);
            return ResultRtn.error("网络拥挤，请稍后重试");
        }
        return ResultRtn.ok("抢购成功");
    }


    public static ConcurrentHashMap<String,Boolean> getParams(){
        return params;
    }

}
